<?php

namespace WeForge\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Queue\WorkerOptions;
use InvalidArgumentException;
use WeForge\Queue\FnfJob;
use WeForge\Queue\Worker;

class WeForgeWorkCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'weforge:work
                            {connection : The name of the queue connection to work}
                            {message : The Base64 encoded message payload}
                            {--delay=0 : The number of seconds to delay failed jobs}
                            {--timeout=0 : The number of seconds a child process can run}
                            {--tries=1 : Number of times to attempt a job before logging it failed}
                            {--force : Force the worker to run even in maintenance mode}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Process a given job';

    /**
     * Indicates whether the command should be shown in the Artisan command list.
     *
     * @var bool
     */
    protected $hidden = true;

    /**
     * The queue worker instance.
     *
     * @var \WeForge\Queue\Worker
     */
    protected $worker;

    /**
     * Indicates if the worker is already listening for events.
     *
     * @var bool
     */
    protected static $listeningForEvents = false;

    /**
     * Create a new console command instance.
     *
     * @param \WeForge\Queue\Worker $worker
     * @param \Illuminate\Contracts\Cache\Repository $cache
     *
     * @return void
     */
    public function __construct(Worker $worker)
    {
        parent::__construct();

        $this->worker = $worker;
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        if ($this->downForMaintenance()) {
            return;
        }

        $this->worker->setCache($this->laravel['cache']->driver());

        if (! static::$listeningForEvents) {
            $this->listenForEvents();

            static::$listeningForEvents = true;
        }

        $this->worker->runWeForgeJob(
            $this->getJob($this->argument('message')),
            $this->argument('connection'),
            $this->gatherWorkerOptions()
        );
    }

    protected function getJob(string $message)
    {
        return new FnfJob(
            $this->laravel,
            $this->worker->getManager()->connection($this->argument('connection')),
            json_decode(base64_decode($message), true)
        );
    }

    /**
     * Gather all of the queue worker options as a single object.
     *
     * @return \Illuminate\Queue\WorkerOptions
     */
    protected function gatherWorkerOptions()
    {
        return new WorkerOptions(
            $this->option('delay'),
            512,
            $this->option('timeout'),
            0,
            $this->option('tries'),
            $this->option('force')
        );
    }

    /**
     * Listen for the queue events in order to update the console output.
     *
     * @return void
     */
    protected function listenForEvents()
    {
        $this->laravel['events']->listen(JobFailed::class, function ($event) {
            $this->logFailedJob($event);
        });
    }

    /**
     * Store a failed job event.
     *
     * @param  \Illuminate\Queue\Events\JobFailed  $event
     * @return void
     */
    protected function logFailedJob(JobFailed $event)
    {
        $this->laravel['queue.failer']->log(
            $event->connectionName,
            $event->job->getQueue() ?: 'default',
            $event->job->getRawBody(),
            $event->exception
        );
    }

    /**
     * Determine if the worker should run in maintenance mode.
     *
     * @return bool
     */
    protected function downForMaintenance()
    {
        return $this->option('force') ? false : $this->laravel->isDownForMaintenance();
    }
}
